using liberipc; 
using liberscribble;

namespace irex {

    /**
     * Application Interface
     * Implement this interface to receive menu/system messages
     */
    public interface Application : Object {
        /**
         * Get the application's main window
         */
        public abstract Gtk.Window getMainWindow();

        /**
         * Called when the a menu item is clicked in the app's menu
         */
        public abstract void onMenuClick(string item, string group, string menu, string state);
        
        /**
         * Called when the app should open a file
         * return a window id, xid, of the window which displays the opened file
         * or a error string, and xid=-1
         */ 
        public abstract bool onFileOpen(string filename, out int xid, out string error);

        /**
         * Called when the app should close a file
         */
        public abstract bool onFileClose(string filename);

        /**
         * Called when the app's window is activated or deactivated.
         * On activation, the app should .show() its menu. 
         */
        public abstract void onWindowChange(int xid, bool activated);

        /**
         * Called when the app should close any open files on device
         */
        public abstract void onPrepareUnmount(string device);

        /**
         * Called when the system unmounts a device
         */
        public abstract void onUnmounted(string device);
        
        /**
         * Called when the system mounts a device
         */
        public abstract void onMounted(string device);

        /**
         * Called when the system is about to hibernate
         */
        public abstract void onPrepareHibernate();

        /**
         * Called when the system locale has changed
         */
        public abstract void onChangedLocale(string locale);

        /**
         * Called when the device orientation changes
         */
        public abstract void onChangedOrientation(string orientation);
    }

    /**
     * ERIPC wrapper
     * User should only need to pass a class implementing the irex.Application
     * interface to recieve ipc messages. 
     */
    public class IPC : Object {
        public liberipc.Context context;
        int[] handler_ids;
        int num_handlers;
        string appname;
        Application app;

        public IPC(string name, string version, Application a) {
            appname = name;
            app = a;
            handler_ids = new int[16];
            context = new Context(name, version, null);
            if (context == null) {
                stderr.printf("Failed to initialize eripc context\n");
            }
            num_handlers = 0;
            set_all_handlers();
        }

        ~IPC() {
            this.unset_all_handlers();
        }

        public string get_appname() {
            return appname;
        }

        public string get_dbus_interface() {
            return "com.irexnet."+appname;
        }
            
        public string get_dbus_path() {
            return "/com/irexnet/"+appname;
        }
            
        public int add_signal_handler(Handler *handler, string interface_name, string message_name, void *userdata) {
            int id=0;
            context.set_signal_handler(handler, userdata, Bus.SESSION, interface_name, message_name, &id);
            handler_ids[num_handlers++] = id;
            return id;
        }

        public int add_message_handler(Handler *handler, string message_name, void *userdata) {
            int id=0;
            context.set_message_handler(handler, userdata, Bus.SESSION, get_dbus_interface(), message_name, &id);
            handler_ids[num_handlers++] = id;
            return id;
        }

        public void unset_all_handlers() {
            for (int i=0;i<num_handlers;i++) {
                context.unset_handler(handler_ids[i]);
            }
        }
        
        public void send_startup_complete() {
            int my_pid = Posix.getpid();
            int my_xid = (int)Gdk.x11_drawable_get_xid(app.getMainWindow().window);
            bool multidoc = true;
            
            context.send_signal_varargs( 
                    Bus.SESSION,
                    get_dbus_path(),
                    "com.irexnet.sysd",
                    "startupComplete",
                    liberipc.Type.STRING, get_appname(),
                    liberipc.Type.INT, my_pid,
                    liberipc.Type.BOOL, multidoc,
                    liberipc.Type.STRING, get_dbus_interface(),
                    liberipc.Type.INT, my_xid,
                    liberipc.Type.INVALID );
        }

        [CCode (instance_pos = -1)]
        void on_menu_item(Context context, EventInfo info) {
            string item = (string)(info.args[0].value.s);
            string group = (string)(info.args[1].value.s);
            string menu = (string)(info.args[2].value.s);
            string state = (string)(info.args[3].value.s);
            app.onMenuClick(item, group, menu, state);
        }

        [CCode (instance_pos = -1)]
        void on_window_activated(Context context, EventInfo info) {
            int xid = (int)(info.args[0].value.s);
            app.onWindowChange(xid, true);
            this.context.reply_bool(info.message_id, true);
        }

        [CCode (instance_pos = -1)]
        void on_window_deactivated(Context context, EventInfo info) {
            int xid = (int)(info.args[0].value.s);
            app.onWindowChange(xid, false);
            this.context.reply_bool(info.message_id, true);
        }
    
        [CCode (instance_pos = -1)]
        void on_file_open(Context context, EventInfo info) {
            string filename = (string)(info.args[0].value.s);
            string error = null;
            int xid = -1;
            
            app.onFileOpen(filename, out xid, out error);
            this.context.reply_varargs(info.message_id, 
                                liberipc.Type.INT, xid,
                                liberipc.Type.STRING, error,
                                liberipc.Type.INVALID);
        }
        
        [CCode (instance_pos = -1)]
        void on_file_close(Context context, EventInfo info) {
            string filename = (string)(info.args[0].value.s);
            this.context.reply_bool(info.message_id, app.onFileClose(filename));
        }

        public void set_all_handlers() {
            add_message_handler((Handler*)on_menu_item,          "menuItemActivated", this);
            add_message_handler((Handler*)on_window_activated,   "activatedWindow",   this);
            add_message_handler((Handler*)on_window_deactivated, "deactivatedWindow", this);
            add_message_handler((Handler*)on_file_open,          "openFile",          this);
            add_message_handler((Handler*)on_file_close,         "closeFile",         this);
        }
    }

    /**
     * PopupMenu menu item
     * Should be created via MenuGroup::addItem()
     */
    public class MenuItem : Object {
        MenuManager manager;
        string label;
        string image;
        string name;
        MenuGroup group;

        public MenuItem(MenuManager mm, MenuGroup group, string name, string label, string image) {
            this.manager = mm;
            this.name = name;
            this.label = label;
            this.image = image;
            this.group = group;
        }
        
        public void realise() {
            manager.add_item(name, group.getName(), image);
            manager.set_item_label(name, group.getName(), label);
        }
    }

    /**
     * PopupMenu menu group
     * Should be created via Menu::addGroup()
     */
    public class MenuGroup : Object {
        MenuManager manager;
        string label;
        string name;
        List<MenuItem> items = new List<MenuItem>();

        public MenuGroup(MenuManager mm, string name, string label) {
            this.manager = mm;
            this.name = mm.getAppName()+"_"+name;
            this.label = label;
        }

        public MenuItem addItem(string name, string label, string image) {
            MenuItem item = new MenuItem(manager, this, name, label, image);
            items.append(item);
            return item;
        }

        public void realise() {
            manager.add_group(name, "", "folder");
            manager.set_group_label(name, label);
            foreach (MenuItem item in items) {
                item.realise();
            }
        }

        public string getName() {
            return name;
        }
    }

    /**
     * PopupMenu menu
     */
    public class Menu : Object {
        MenuManager manager;
        string label;
        string name;
        MenuGroup[3] groups = new MenuGroup[3];
        int numGroups = 0;

        public Menu(MenuManager mm, string name, string label) {
            this.manager = mm;
            this.name = mm.getAppName()+"_"+name;
            this.label = label;
        }
        
        ~Menu() {
            manager.remove_menu(name);
        }

        public MenuGroup addGroup(string name, string label) {
            MenuGroup group = new MenuGroup(manager, name, label);
            groups[numGroups++] = group;
            return group;
        }

        public void realise() {
            for (int i=0;i<3;i++) {
                if (groups[i]!=null)
                    groups[i].realise();
            }
            manager.add_menu(name, 
                numGroups>0 ? groups[0].getName() : null,
                numGroups>1 ? groups[1].getName() : null,
                numGroups>2 ? groups[2].getName() : null);
            manager.set_menu_label(name, label);
        }

        public void show() {
            manager.show_menu(name);
        }
    }

    /**
     * Wrapper around the IPC menu functionality.
     * Don't call directly, rather use Menu/MenuGroup/MenuItem
     */
    public class MenuManager : Object {
        IPC ipc;

        public MenuManager(IPC ipc) {
            this.ipc = ipc;
        }

        public string getAppName() {
            return ipc.get_appname();
        }

        public bool add_menu(string name, string group1, string? group2, string? group3) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "addMenu",
                        liberipc.Type.STRING, name,
                        liberipc.Type.STRING, "",
                        liberipc.Type.STRING, ipc.get_dbus_interface(),
                        liberipc.Type.STRING, group1,
                        liberipc.Type.STRING, group2,
                        liberipc.Type.STRING, group3,
                        liberipc.Type.STRING, "",
                        liberipc.Type.INVALID );
            return true;
        }
        
        public bool add_group(string name, string parent, string image) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "addGroup",
                        liberipc.Type.STRING, name,
                        liberipc.Type.STRING, parent,
                        liberipc.Type.STRING, "",
                        liberipc.Type.STRING, image,
                        liberipc.Type.INVALID );
            return true;
        }
        
        public bool add_item(string name, string parent, string image) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "addItem",
                        liberipc.Type.STRING, name,
                        liberipc.Type.STRING, parent,
                        liberipc.Type.STRING, "",
                        liberipc.Type.STRING, image,
                        liberipc.Type.INVALID );
            return true;
        }

        public bool set_menu_label(string name, string label) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "setMenuLabel",
                        liberipc.Type.STRING, name,
                        liberipc.Type.STRING, label,
                        liberipc.Type.INVALID );
            return true;
        }

        public bool set_group_label(string name, string label) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "setGroupLabel",
                        liberipc.Type.STRING, name,
                        liberipc.Type.STRING, label,
                        liberipc.Type.INVALID );
            return true;
        }

        public bool set_item_label(string name, string parent, string label) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "setItemLabel",
                        liberipc.Type.STRING, name,
                        liberipc.Type.STRING, parent,
                        liberipc.Type.STRING, label,
                        liberipc.Type.INVALID );
            return true;
        }

        public bool show_menu(string name) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "showMenu",
                        liberipc.Type.STRING, name,
                        liberipc.Type.INVALID );
            return true;
        }

        public bool remove_menu(string name) {
            ipc.context.send_varargs(
                        null,
                        null,
                        Bus.SESSION,
                        "com.irexnet.popupmenu",
                        "removeMenu",
                        liberipc.Type.STRING, name,
                        liberipc.Type.INVALID );
            return true; 
        }
    }

    public class ScribbleManager : GLib.Object {
        private liberscribble.Stroke stroke = null;
        private liberscribble.StrokeAttributes attrs;
        private int scribble_state = -1;

        public ScribbleManager() {
            liberscribble.driver_init();
            attrs.size_id =  3;
        }

        ~ScribbleManager() {
            liberscribble.driver_close();
        }

        public void pen_down(int x, int y) {
            if (scribble_state<0) {
                scribble_state = 0;
                begin_stroke(x, y);
            }
        }

        public void pen_up(int x, int y) {
            if (scribble_state>=0) {
                end_stroke(x, y);
                scribble_state = -1;
            }
        }

        public void pen_move(int x, int y) {
            if (scribble_state==0) {
                scribble_state = 1;
            }
            if (scribble_state==1) {
                move_stroke(x, y);
            }
        }

        public void pen_set_size(int size) {
            attrs.size_id =  size;
        }

        public void begin_stroke(int x, int y) {
            attrs.color = liberscribble.DeviceColor.BLACK;
            attrs.layer = 0;
            attrs.zoom =  1.0f;
            attrs.shape_id = 0;
            attrs.points_number = 0;
            if (stroke==null)
                stroke = new Stroke(attrs);
            add_point(x, y, false);
        }

        public void move_stroke(int x, int y) {
            add_point(x, y, false);
        }

        public void end_stroke(int x, int y) {
            add_point(x, y, true);
            draw_stroke();
            stroke = null;
        }

        public void add_point( int x, int y, bool end) {
            liberscribble.DevicePoint point = liberscribble.DevicePoint();
            point.point.x = x;
            point.point.y = y;
            point.pressure = 0;
            stroke.add_point(point);
            stroke.driver_draw_point(point, end);
        }

        public void draw_stroke() {
            /*
            int count;
            liberscribble.Point[] points;
            count  = stroke.get_point_count();
            points = stroke.get_point_data();
            */
        }
    }
    
}
